package application

import (
	"context"
	"encoding/json"
	goErrors "errors"
	"fmt"
	"github.com/edgexfoundry/edgex-go/internal/app/core/metadata/container"
	"github.com/edgexfoundry/edgex-go/internal/app/dtos/responses"
	"github.com/edgexfoundry/edgex-go/internal/app/models"
	metadataContainer "github.com/edgexfoundry/edgex-go/internal/core/metadata/container"
	"github.com/edgexfoundry/edgex-go/internal/pkg/correlation"
	bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/container"
	"github.com/edgexfoundry/go-mod-bootstrap/v2/di"
	"github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger"
	"github.com/edgexfoundry/go-mod-core-contracts/v2/common"
	"github.com/edgexfoundry/go-mod-core-contracts/v2/dtos"
	"github.com/edgexfoundry/go-mod-core-contracts/v2/errors"
	edgexModel "github.com/edgexfoundry/go-mod-core-contracts/v2/models"
	"github.com/edgexfoundry/go-mod-messaging/v2/pkg/types"
)

func DevicesGetByLabels(labels []string, productId string, offset, limit int, ctx context.Context, dic *di.Container) (devices []models.Device, totalCount uint32, err errors.EdgeX) {
	dbClient := container.DBClientFrom(dic.Get)
	lc := bootstrapContainer.LoggingClientFrom(dic.Get)

	if productId != "" {
		devices, totalCount, err = dbClient.DevicesGetByProduct(productId, offset, limit)
		if err != nil {
			return nil, 0, errors.NewCommonEdgeXWrapper(err)
		}
	} else {
		devices, totalCount, err = dbClient.DevicesGetByLabels(labels, offset, limit)
		if err != nil {
			return nil, 0, errors.NewCommonEdgeXWrapper(err)
		}
	}

	lc.Debugf("Devices query on DB successfully. Correlation-id: %s ", correlation.FromContext(ctx))
	return devices, totalCount, nil
}

func DevicesGetByName(name, productId string, offset, limit int, ctx context.Context, dic *di.Container) ([]models.Device, uint32, errors.EdgeX) {
	dbClient := container.DBClientFrom(dic.Get)
	lc := bootstrapContainer.LoggingClientFrom(dic.Get)

	devices, totalCount, err := dbClient.DevicesGetByName(name, productId, offset, limit)
	if err != nil {
		return nil, totalCount, errors.NewCommonEdgeXWrapper(err)
	}

	lc.Debugf("Devices query on DB successfully. Correlation-id: %s ", correlation.FromContext(ctx))
	return devices, totalCount, nil
}

func DevicesAdd(device models.Device, ctx context.Context, dic *di.Container) (string, errors.EdgeX) {
	dbClient := container.DBClientFrom(dic.Get)
	lc := bootstrapContainer.LoggingClientFrom(dic.Get)

	//通过edgex验证  必须包含一条协议
	if device.Protocols == nil || len(device.Protocols) == 0 {
		device.Protocols = map[string]edgexModel.ProtocolProperties{
			"other": {},
		}
	}

	err := validateDeviceCallback(ctx, dic, responses.ToDeviceDTO(device))
	if err != nil {
		return "", errors.NewCommonEdgeXWrapper(err)
	}

	d, err := dbClient.DeviceAdd(device)
	if err != nil {
		return "", errors.NewCommonEdgeXWrapper(err)
	}

	lc.Debugf("Devices add on DB successfully. Correlation-id: %s ", correlation.FromContext(ctx))

	res := responses.ToDeviceDTO(d)
	go addDeviceCallback(ctx, dic, res)

	go publishDeviceSystemEvent(common.DeviceSystemEventActionAdd, d.ServiceName, d, ctx, lc, dic)
	return d.Id, nil
}

var noMessagingClientError = goErrors.New("MessageBus Client not available. Please update RequireMessageBus and MessageQueue configuration to enable sending System Events via the EdgeX MessageBus")

func publishDeviceSystemEvent(action string, owner string, d models.Device, ctx context.Context, lc logger.LoggingClient, dic *di.Container) {
	device := dtos.FromDeviceModelToDTO(d.Device)
	systemEvent := dtos.NewSystemEvent(common.DeviceSystemEventType, action, common.CoreMetaDataServiceKey, owner, nil, device)

	messagingClient := bootstrapContainer.MessagingClientFrom(dic.Get)
	if messagingClient == nil {
		// For 2.x this is a warning due to backwards compatability
		// TODO: For change this to be Errorf for EdgeX 3.0
		lc.Warnf("unable to publish Device System Event: %v", noMessagingClientError)
		return
	}

	config := metadataContainer.ConfigurationFrom(dic.Get)
	publishTopic := fmt.Sprintf("%s/%s/%s/%s/%s/%s",
		config.MessageQueue.PublishTopicPrefix,
		systemEvent.Source,
		systemEvent.Type,
		systemEvent.Action,
		systemEvent.Owner,
		device.ProfileName)

	payload, _ := json.Marshal(systemEvent)
	envelope := types.NewMessageEnvelope(payload, ctx)
	// Correlation ID and Content type are set by the above factory function from the context of the request that
	// triggered this System Event. We'll keep that Correlation ID, but need to make sure the Content Type is set appropriate
	// for how the payload was encoded above.
	envelope.ContentType = common.ContentTypeJSON

	if err := messagingClient.Publish(envelope, publishTopic); err != nil {
		lc.Errorf("unable to publish '%s' Device System Event for device '%s' to topic '%s': %v", action, device.Name, publishTopic, err)
		return
	}

	lc.Debugf("Published the '%s' Device System Event for device '%s' to topic '%s'", action, device.Name, publishTopic)
}

func DeviceGetById(deviceId string, ctx context.Context, dic *di.Container) (models.Device, errors.EdgeX) {
	dbClient := container.DBClientFrom(dic.Get)
	lc := bootstrapContainer.LoggingClientFrom(dic.Get)

	device, err := dbClient.DeviceGetById(deviceId)
	if err != nil {
		return models.Device{}, errors.NewCommonEdgeXWrapper(err)
	}

	lc.Debugf("Devices query by id on DB successfully. Correlation-id: %s ", correlation.FromContext(ctx))
	return device, nil
}

func DeviceUpdate(device models.Device, ctx context.Context, dic *di.Container) errors.EdgeX {
	dbClient := container.DBClientFrom(dic.Get)
	lc := bootstrapContainer.LoggingClientFrom(dic.Get)

	err := dbClient.DeviceUpdate(device)
	if err != nil {
		return errors.NewCommonEdgeXWrapper(err)
	}

	lc.Debugf("Device update on DB successfully. Correlation-id: %s ", correlation.FromContext(ctx))
	go SyncAutoEventsToDevice(device.Id, dic)

	return nil
}

func DeviceDelete(deviceId []string, ctx context.Context, dic *di.Container) errors.EdgeX {
	dbClient := container.DBClientFrom(dic.Get)
	lc := bootstrapContainer.LoggingClientFrom(dic.Get)

	devices, err := dbClient.DeviceDelete(deviceId...)
	if err != nil {
		return errors.NewCommonEdgeXWrapper(err)
	}

	lc.Debugf("Device update on DB successfully. Correlation-id: %s ", correlation.FromContext(ctx))

	for _, device := range devices {
		go deleteDeviceCallback(ctx, dic, device)

		go publishDeviceSystemEvent(common.DeviceSystemEventActionDelete, device.ServiceName, device, ctx, lc, dic)
	}
	return nil
}

// SyncAutoEventsToDevice 同步设备元信息
func SyncAutoEventsToDevice(id string, dic *di.Container) {

	lc := bootstrapContainer.LoggingClientFrom(dic.Get)
	if id == "" {
		lc.Errorf(
			"SyncAutoEventsToDevice failed. Because of device id is nil",
			id,
		)
	}

	dbClient := container.DBClientFrom(dic.Get)
	err := dbClient.SyncAutoEventsToDevice(id)
	if err != nil {
		lc.Errorf(
			fmt.Sprintf("SyncAutoEventsToDevice failed. Because of %v", err),
			id,
		)
	}

	lc.Errorf(
		"SyncAutoEventsToDevice success. ",
		id,
	)
	return
}

// CountByDeviceStatus 设备数量统计
func CountByDeviceStatus(ctx context.Context, dic *di.Container) (models.DeviceCount, errors.EdgeX) {

	dbClient := container.DBClientFrom(dic.Get)
	lc := bootstrapContainer.LoggingClientFrom(dic.Get)

	//在线事件区间5min
	duration := 5 * 60 * 1000
	deviceCount, err := dbClient.CountByDeviceStatus(int32(duration))
	if err != nil {
		return deviceCount, errors.NewCommonEdgeXWrapper(err)
	}

	lc.Debugf("Count by device status on DB successfully. Correlation-id: %s ", correlation.FromContext(ctx))
	return deviceCount, nil
}
